 package com.gmrz.uaf.policy.verification;
 
 import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.AAID;
import com.gmrz.uaf.protocol.v1.schema.AuthenticatorSpec;
import com.gmrz.uaf.protocol.v1.schema.MatchCriteria;
import com.gmrz.uaf.protocol.v1.schema.VerificationMethodDescriptor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;
import java.util.Set;
 
 public class Matcher
 {
   private static final Logger LOG = LogManager.getLogger(Matcher.class);
   private static long USER_VERIFICATION_DERIVATION_NOT_POSSIBLE = 0L;
 
   public static boolean compare(MatchCriteria matchCriteria, AuthenticatorSpec authenticatorMetadata)
     throws PolicyVerificationException
   {
     try
     {
       // 判空校验
       verifyInputs(matchCriteria, authenticatorMetadata);
 
       boolean isMatched = false;
 
       if ((matchCriteria.getAAID() != null) && (matchCriteria.getAAID().size() > 0)) {
         isMatched = false;
         for (AAID aaid : matchCriteria.getAAID()) {
           // 判断“此已注册的认证数据”是否存在于“匹配标准”的AAID列表中
           if (aaid.format().equalsIgnoreCase(authenticatorMetadata.getAAID().format())) {
             LOG.debug("The AAID : {},  is found in list of the matchCriteria supported list of AAIDs ", aaid.format());
             isMatched = true;
             break;
           }
         }
         if (!isMatched) {
           LOG.debug("Following AAID did not match the criteria : {}", authenticatorMetadata.getAAID().format());
           return false;
         }
 
       }
 
       Integer mcVersion = matchCriteria.getAuthenticatorVersion();
       Integer mdVersion = authenticatorMetadata.getAuthenticatorVersion();
       if ((mcVersion != null) &&
         (mcVersion.intValue() < mdVersion.intValue())) {
         LOG.debug("Following AuthenticatorVersion[{}] in match criteria is less than the value in metadata[{}]", mcVersion, mdVersion);
         return false;
       }
 
       if ((matchCriteria.getVendorID() != null) && (matchCriteria.getVendorID().size() > 0)) {
         // 判断“此已注册的认证数据”的认证器的厂商代码是否存在于“匹配标准”的厂商代码列表中
         isMatched = PolicyVerificationUtil.containsStringInList(matchCriteria.getVendorID(), authenticatorMetadata.getAAID().getVendorCode());
         if (!isMatched) {
           LOG.debug("Following vendorID did not match the criteria : {}", authenticatorMetadata.getAAID().format());
           return false;
         }
 
       }
 
       if ((matchCriteria.getKeyProtection() != null) && 
         ((matchCriteria.getKeyProtection().intValue() & authenticatorMetadata.getKeyProtection().intValue()) <= 0)) {
         LOG.debug("Following key protection type : {} did not match the criteria keyProtection type : {}", authenticatorMetadata.getKeyProtection(), matchCriteria.getKeyProtection());
         return false;
       }
 
       if ((matchCriteria.getMatcherProtection() != null) && 
         ((matchCriteria.getMatcherProtection().intValue() & authenticatorMetadata.getMatcherProtection().intValue()) <= 0)) {
         LOG.debug("Following Matcher protection type : {}, did not match the criteria matcherProtection type : {} ", authenticatorMetadata.getMatcherProtection(), matchCriteria.getMatcherProtection());
 
         return false;
       }
 
       if ((matchCriteria.getAttachment() != null) && 
         ((matchCriteria.getAttachment().longValue() & authenticatorMetadata.getAttachmentHint().longValue()) <= 0L)) {
         LOG.debug("Following Attachment hint type : {}, did not match the criteria Attachment hint type : ", authenticatorMetadata.getAttachmentHint(), matchCriteria.getAttachment());
         return false;
       }
 
       if ((matchCriteria.getTcDisplay() != null) && 
         ((matchCriteria.getTcDisplay().intValue() & authenticatorMetadata.getSecureDisplay().intValue()) <= 0)) {
         LOG.debug("Following SecureDisplay type : {} did not match the criteria SecureDisplay type : {} ", authenticatorMetadata.getSecureDisplay(), matchCriteria.getTcDisplay());
         return false;
       }
 
       if ((matchCriteria.getSupportedAuthAlgs() != null) && (matchCriteria.getSupportedAuthAlgs().size() > 0))
       {
         isMatched = PolicyVerificationUtil.containsIntegerInList(matchCriteria.getSupportedAuthAlgs(), authenticatorMetadata.getAlgorithm());
         if (!isMatched) {
           LOG.debug("Following supported algorithm did not match the criteria : {}", Integer.valueOf(authenticatorMetadata.getAlgorithm()));
           return false;
         }
 
       }
 
       if ((matchCriteria.getSupportedSchemes() != null) && (matchCriteria.getSupportedSchemes().size() > 0)) {
         isMatched = PolicyVerificationUtil.containsStringInList(matchCriteria.getSupportedSchemes(), authenticatorMetadata.getAssertionScheme());
 
         if (!isMatched) {
           LOG.debug("Following supported scheme did not match the criteria : {}", authenticatorMetadata.getAssertionScheme());
           return false;
         }
 
       }
 
       if ((matchCriteria.getPreferredAttestationTypes() != null) && (matchCriteria.getPreferredAttestationTypes().size() > 0)) {
         isMatched = false;
         for (Integer preferredAttestationType : matchCriteria.getPreferredAttestationTypes()) {
           if (PolicyVerificationUtil.containsIntegerInList(authenticatorMetadata.getAttestationTypes(), preferredAttestationType.intValue())) {
             isMatched = true;
             LOG.debug(" Attestation type matched : {} ", preferredAttestationType);
             break;
           }
         }
         if (!isMatched) {
           LOG.debug("AttestionTypes in Authenticator Metadata  did not match the criteria preferred AttestationType : ");
           return false;
         }
 
       }
 
       if (matchCriteria.getUserVerification() != null) {
         isMatched = checkUserVerificationMethods(matchCriteria, authenticatorMetadata);
         if (!isMatched) {
           LOG.debug("User verification methods in policy did not match the criteria; userVerification : {}", matchCriteria.getUserVerification());
           return false;
         }
 
       }
 
       return true;
     }
     catch (PolicyVerificationException pve) {
       LOG.error(" Failed to map Autenticator authenticatorMetadata to MatchCriteria for Policy Verification : {}", pve.getMessage());
       throw pve;
     }
     catch (Exception e) {
       LOG.error(" Unable to map Autenticator authenticatorMetadata to MatchCriteria for Policy Verification : {}", e.getMessage());
     }throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, " Unable to map Autenticator authenticatorMetadata to MatchCriteria for Policy Verification");
   }
 
   private static boolean checkUserVerificationMethods(MatchCriteria matchCriteria, AuthenticatorSpec authenticatorMetadata)
     throws PolicyVerificationException
   {
     List verificationMethods = authenticatorMetadata.getUserVerficationMethods();
 
     long userVerificationFromMetadata = deriveUserVerificationFromMetadata(verificationMethods);
 
     if (userVerificationFromMetadata == USER_VERIFICATION_DERIVATION_NOT_POSSIBLE)
     {
       return false;
     }

     return (userVerificationFromMetadata == matchCriteria.getUserVerification().longValue()) || (((userVerificationFromMetadata & Constants.UserVerification.USER_VERIFY_ALL.getValue()) == 0L) && ((matchCriteria.getUserVerification().longValue() & Constants.UserVerification.USER_VERIFY_ALL.getValue()) == 0L) && ((userVerificationFromMetadata & matchCriteria.getUserVerification().longValue()) != 0L));

   }
 
   private static void verifyInputs(MatchCriteria matchCriteria, AuthenticatorSpec authenticatorMetadata) throws PolicyVerificationException
   {
     if (matchCriteria == null) {
       LOG.error("MatchCriteria cannot be null");
       throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Policy verification failed");
     }
 
     if (authenticatorMetadata == null) {
       LOG.error("AuthenticatorSpec cannot be null");
       throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Policy verification failed");
     }
   }
 
   private static long deriveUserVerificationFromMetadata(List<Set<VerificationMethodDescriptor>> verificationMethodsList) throws PolicyVerificationException
   {
     if ((verificationMethodsList == null) || (verificationMethodsList.size() == 0)) {
       LOG.error("verificationMethods cannot be null/empty");
       throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Policy verification failed");
     }
     long derivedUserVerificationVal = 0L;
     if (verificationMethodsList.size() == 1) {
       for (Set<VerificationMethodDescriptor> verificationMethodSet : verificationMethodsList) {
         for (VerificationMethodDescriptor verificationMethod : verificationMethodSet)
         {
           LOG.debug("Value of user verification method : {}", Long.valueOf(verificationMethod.getUserVerification()));
           derivedUserVerificationVal |= verificationMethod.getUserVerification();
 
           derivedUserVerificationVal |= Constants.UserVerification.USER_VERIFY_ALL.getValue();
         }
       }
       LOG.debug("VerificationMethod List contains only 1 set, the derived value: {}", Long.valueOf(derivedUserVerificationVal));
     }
     else {
       for (Set verificationMethodSet : verificationMethodsList)
       {
         if (verificationMethodSet.size() > 1) {
           LOG.debug("direct derivation is not possible, Must generate MatchCriteria object by providing a list of matching AAIDs.");
           return USER_VERIFICATION_DERIVATION_NOT_POSSIBLE;
         }if (verificationMethodSet.size() == 1)
         {
           VerificationMethodDescriptor verificationMethod = (VerificationMethodDescriptor)verificationMethodSet.iterator().next();
           derivedUserVerificationVal |= verificationMethod.getUserVerification();
           LOG.debug("Value of user verification method : {}", Long.valueOf(verificationMethod.getUserVerification()));
 
           derivedUserVerificationVal |= Constants.UserVerification.USER_VERIFY_ALL.getValue();
         }
         else if (verificationMethodSet.size() != 0);
       }
     }
 
     LOG.debug("The derived value of userVerification from the metadata statement : {} ", Long.valueOf(derivedUserVerificationVal));
 
     return derivedUserVerificationVal;
   }
 }
